package com.tl.satoken.netty;

import com.tl.satoken.domain.entity.Player;
import com.tl.satoken.listener.WebSocketEventListener;
import com.tl.satoken.proto.GameProto;
import com.tl.satoken.service.RoomService;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.http.websocketx.*;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;

import java.util.List;
import java.util.concurrent.ConcurrentHashMap;

@Slf4j
@Component
@ChannelHandler.Sharable
public class ProtobufWebSocketHandler extends SimpleChannelInboundHandler<Object> {

    private static final ConcurrentHashMap<String, ChannelHandlerContext> clients = new ConcurrentHashMap<>();

    @Autowired
    private RoomService roomService;
    
    @Autowired
    private WebSocketEventListener webSocketEventListener;
    
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    @Override
    protected void channelRead0(ChannelHandlerContext ctx, Object msg) {
        if (msg instanceof BinaryWebSocketFrame) {
            String clientId = ctx.channel().id().asShortText();
            log.info("收到客户端[{}]二进制消息", clientId);
            
            BinaryWebSocketFrame binaryFrame = (BinaryWebSocketFrame) msg;
            ByteBuf content = binaryFrame.content();
            byte[] bytes = new byte[content.readableBytes()];
            content.readBytes(bytes);
            
            try {
                GameProto.GameMessage gameMessage = GameProto.GameMessage.parseFrom(bytes);
                handleProtobufMessage(ctx, clientId, gameMessage);
            } catch (Exception e) {
                log.error("解析protobuf二进制消息失败", e);
                sendError(ctx, "消息格式错误");
            }
        } else if (msg instanceof CloseWebSocketFrame) {
            log.info("客户端请求关闭连接");
            ctx.channel().close();
        } else if (msg instanceof PingWebSocketFrame) {
            log.info("收到Ping消息");
            ctx.channel().writeAndFlush(new PongWebSocketFrame());
        }
    }

    
    private void handleProtobufMessage(ChannelHandlerContext ctx, String clientId, GameProto.GameMessage message) {
        if (roomService == null || webSocketEventListener == null) {
            return;
        }
        
        try {
            GameProto.MessageType type = message.getBase().getType();
            log.info("处理protobuf消息类型: {}", type);
            
            switch (type) {
                case JOIN_GAME:
                    handleJoinGame(ctx, clientId, message);
                    break;
                case LEAVE_GAME:
                    handleLeaveGame(ctx, clientId, message);
                    break;
                case UPDATE_PLAYER:
                    handleUpdatePlayer(ctx, clientId, message);
                    break;
                case EMOJI_SENT:
                    handleEmojiSent(ctx, clientId, message);
                    break;
                case HEARTBEAT:
                    handleHeartbeat(ctx, clientId, message);
                    break;
                default:
                    log.warn("未知protobuf消息类型: {}", type);
            }
        } catch (Exception e) {
            log.error("处理protobuf消息失败", e);
            sendError(ctx, "处理消息失败: " + e.getMessage());
        }
    }
    
    private void handleJoinGame(ChannelHandlerContext ctx, String clientId, GameProto.GameMessage message) {
        try {
            GameProto.JoinGameMessage joinGame = message.getJoinGame();
            GameProto.Player protoPlayer = joinGame.getPlayer();
            
            // 转换为内部Player对象
            Player player = convertProtoPlayerToPlayer(protoPlayer);
            player.setSessionId(clientId);
            player.setChannel(ctx);
            
            log.info("玩家通过protobuf WebSocket加入游戏：{}，会话ID: {}", player.getUsername(), clientId);
            webSocketEventListener.addSessionPlayer(clientId, player);

            String playerKey = "game:player:" + player.getId();
            String roomId = (String)redisTemplate.opsForValue().get(playerKey);
            if (roomId != null) {
                List<Player> roomPlayers = roomService.getPlayersInRoom(roomId);
                broadcastJoinGame(roomId, roomPlayers, player.getId());
                log.info("玩家 {} 已通过HTTP加入房间 {}，通过protobuf WebSocket连接成功", player.getUsername(), roomId);
            } else {
                var result = roomService.joinRoom(player);
                if (result.isSuccess()) {
                    List<Player> roomPlayers = roomService.getPlayersInRoom(result.getRoomId());
                    broadcastJoinGame(result.getRoomId(), roomPlayers, player.getId());
                    log.info("玩家 {} 成功加入房间 {}", player.getUsername(), result.getRoomId());
                } else {
                    log.warn("玩家 {} 加入房间失败: {}", player.getUsername(), result.getMessage());
                    sendError(ctx, result.getMessage());
                }
            }
        } catch (Exception e) {
            log.error("处理protobuf加入游戏消息失败", e);
            sendError(ctx, "加入游戏失败: " + e.getMessage());
        }
    }
    
    private void handleLeaveGame(ChannelHandlerContext ctx, String clientId, GameProto.GameMessage message) {
        try {
            GameProto.LeaveGameMessage leaveGame = message.getLeaveGame();
            GameProto.Player protoPlayer = leaveGame.getPlayer();
            
            Player player = convertProtoPlayerToPlayer(protoPlayer);
            log.info("玩家主动下线：{}，会话ID: {}", player.getUsername(), clientId);
            webSocketEventListener.removeSessionPlayer(clientId);
            
            if (roomService.leaveRoom(player.getId())) {
                String roomId = player.getRoomId();
                List<Player> remainingPlayers = roomService.getPlayersInRoom(roomId);
                broadcastLeaveGame(roomId, remainingPlayers, player.getId());
                log.info("房间 {} 剩余玩家数量: {}", roomId, remainingPlayers.size());
            }
        } catch (Exception e) {
            log.error("处理protobuf离开游戏消息失败", e);
        }
    }
    
    private void handleUpdatePlayer(ChannelHandlerContext ctx, String clientId, GameProto.GameMessage message) {
        try {
            GameProto.UpdatePlayerMessage updatePlayer = message.getUpdatePlayer();
            GameProto.Player protoPlayer = updatePlayer.getPlayer();
            
            Player player = convertProtoPlayerToPlayer(protoPlayer);
            log.info("玩家移动：{}，位置：({}, {})，会话ID: {}", 
                player.getUsername(), player.getPosition().getX(), player.getPosition().getY(), clientId);
            
            // 更新session中的玩家信息
            Player sessionPlayer = webSocketEventListener.getPlayer(clientId);
            if (sessionPlayer != null) {
                sessionPlayer.setPosition(player.getPosition());
                sessionPlayer.setStatus(player.getStatus());
                sessionPlayer.setRoomId(player.getRoomId());
            }
            
            if (roomService.updatePlayerPosition(player.getId(), player)) {
                String roomId = "Room_1";
                broadcastPlayerMove(roomId, player);
            } else {
                log.warn("玩家 {} 位置更新失败", player.getUsername());
            }
        } catch (Exception e) {
            log.error("处理protobuf玩家移动消息失败", e);
        }
    }
    
    private void handleEmojiSent(ChannelHandlerContext ctx, String clientId, GameProto.GameMessage message) {
        try {
            Player sessionPlayer = webSocketEventListener.getPlayer(clientId);
            if (sessionPlayer == null) {
                log.warn("发送表情包的玩家会话不存在: {}", clientId);
                return;
            }
            
            String roomId = sessionPlayer.getRoomId();
            if (roomId == null) {
                log.warn("玩家 {} 不在任何房间中", sessionPlayer.getUsername());
                return;
            }
            
            GameProto.EmojiMessage emojiMessage = message.getEmojiSent();
            log.info("玩家 {} 发送表情包: {}，房间: {}", sessionPlayer.getUsername(), emojiMessage.getEmoji(), roomId);
            
            // 广播表情包消息给房间内其他玩家
            List<Player> roomPlayers = roomService.getPlayersInRoom(roomId);
            broadcastEmojiMessage(roomId, message, sessionPlayer.getId());
            
        } catch (Exception e) {
            log.error("处理protobuf表情包消息失败", e);
        }
    }
    
    private void handleHeartbeat(ChannelHandlerContext ctx, String clientId, GameProto.GameMessage message) {
        try {
            GameProto.HeartbeatMessage heartbeat = message.getHeartbeat();
            log.debug("收到客户端[{}]心跳消息，时间戳: {}", clientId, heartbeat.getTimestamp());
            
            // 发送心跳响应
            GameProto.GameMessage response = GameProto.GameMessage.newBuilder()
                .setBase(GameProto.BaseMessage.newBuilder()
                    .setType(GameProto.MessageType.HEARTBEAT)
                    .setTimestamp(System.currentTimeMillis())
                    .setFrom("SERVER")
                    .build())
                .setHeartbeat(GameProto.HeartbeatMessage.newBuilder()
                    .setTimestamp(System.currentTimeMillis())
                    .build())
                .build();
            
            sendProtobufMessage(ctx, response);
        } catch (Exception e) {
            log.error("处理protobuf心跳消息失败", e);
        }
    }
    
    private Player convertProtoPlayerToPlayer(GameProto.Player protoPlayer) {
        Player player = new Player();
        player.setId(protoPlayer.getId());
        player.setUsername(protoPlayer.getUsername());
        player.setStatus(protoPlayer.getStatus());
        player.setRoomId(protoPlayer.getRoomId());
        player.setSessionId(protoPlayer.getSessionId());
        
        // 转换位置信息
        if (protoPlayer.hasPosition()) {
            GameProto.Position protoPosition = protoPlayer.getPosition();
            com.tl.satoken.domain.entity.Position position = new com.tl.satoken.domain.entity.Position(
                (int)protoPosition.getX(), (int)protoPosition.getY()
            );
            player.setPosition(position);
        }
        
        return player;
    }
    
    private GameProto.Player convertPlayerToProtoPlayer(Player player) {
        GameProto.Player.Builder builder = GameProto.Player.newBuilder()
            .setId(player.getId())
            .setUsername(player.getUsername())
            .setStatus(player.getStatus())
            .setRoomId(player.getRoomId());
        
        // 转换位置信息
        if (player.getPosition() != null) {
            GameProto.Position position = GameProto.Position.newBuilder()
                .setX(player.getPosition().getX())
                .setY(player.getPosition().getY())
                .setZ(0)
                .build();
            builder.setPosition(position);
        }
        
        return builder.build();
    }

    
    private void broadcastRoomUpdate(String roomId, List<Player> players, String excludePlayerId, GameProto.MessageType messageType) {
        try {
            GameProto.RoomUpdateMessage.Builder roomUpdateBuilder = GameProto.RoomUpdateMessage.newBuilder();
            for (Player player : players) {
                if (excludePlayerId != null && excludePlayerId.equals(player.getId())) {
                    continue;
                }
                roomUpdateBuilder.addPlayers(convertPlayerToProtoPlayer(player));
            }
            
            GameProto.GameMessage updateMessage = GameProto.GameMessage.newBuilder()
                .setBase(GameProto.BaseMessage.newBuilder()
                    .setType(messageType)
                    .setTimestamp(System.currentTimeMillis())
                    .setFrom("SERVER")
                    .setRoomId(roomId)
                    .build())
                .setRoomUpdate(roomUpdateBuilder.build())
                .build();
            
            for (Player player : players) {
                if (excludePlayerId != null && excludePlayerId.equals(player.getId())) {
                    continue;
                }
                
                Player sessionPlayer = webSocketEventListener.getPlayerById(player.getId());
                if (sessionPlayer != null && sessionPlayer.getChannel() != null && sessionPlayer.getChannel().channel().isActive()) {
                    sendProtobufMessage(sessionPlayer.getChannel(), updateMessage);
                }
            }
        } catch (Exception e) {
            log.error("广播protobuf房间更新失败", e);
        }
    }
    
    // 专门用于处理加入游戏的情况
    private void broadcastJoinGame(String roomId, List<Player> players, String excludePlayerId) {
        try {
            // 构建包含所有玩家的消息
            GameProto.RoomUpdateMessage.Builder roomUpdateBuilder = GameProto.RoomUpdateMessage.newBuilder();
            for (Player player : players) {
                roomUpdateBuilder.addPlayers(convertPlayerToProtoPlayer(player));
            }
            
            GameProto.GameMessage updateMessage = GameProto.GameMessage.newBuilder()
                .setBase(GameProto.BaseMessage.newBuilder()
                    .setType(GameProto.MessageType.JOIN_GAME)
                    .setTimestamp(System.currentTimeMillis())
                    .setFrom("SERVER")
                    .setRoomId(roomId)
                    .build())
                .setRoomUpdate(roomUpdateBuilder.build())
                .build();
            
            // 发送给房间内所有玩家（包括新加入的玩家）
            for (Player player : players) {
                Player sessionPlayer = webSocketEventListener.getPlayerById(player.getId());
                if (sessionPlayer != null && sessionPlayer.getChannel() != null && sessionPlayer.getChannel().channel().isActive()) {
                    sendProtobufMessage(sessionPlayer.getChannel(), updateMessage);
                }
            }
        } catch (Exception e) {
            log.error("广播protobuf加入游戏消息失败", e);
        }
    }
    
    // 专门用于处理离开游戏的情况
    private void broadcastLeaveGame(String roomId, List<Player> players, String excludePlayerId) {
        broadcastRoomUpdate(roomId, players, excludePlayerId, GameProto.MessageType.LEAVE_GAME);
    }
    
    private void broadcastPlayerMove(String roomId, Player movedPlayer) {
        try {
            GameProto.PlayerMoveMessage playerMove = GameProto.PlayerMoveMessage.newBuilder()
                .setPlayer(convertPlayerToProtoPlayer(movedPlayer))
                .build();
            
            GameProto.GameMessage moveMessage = GameProto.GameMessage.newBuilder()
                .setBase(GameProto.BaseMessage.newBuilder()
                    .setType(GameProto.MessageType.PLAYER_MOVE)
                    .setTimestamp(System.currentTimeMillis())
                    .setFrom("SERVER")
                    .setRoomId(roomId)
                    .build())
                .setPlayerMove(playerMove)
                .build();
            
            List<Player> roomPlayers = roomService.getPlayersInRoom(roomId);
            int broadcastCount = 0;
            
            for (Player player : roomPlayers) {
                if (movedPlayer.getId().equals(player.getId())) {
                    continue;
                }

                Player sessionPlayer = webSocketEventListener.getPlayerById(player.getId());
                if (sessionPlayer != null && sessionPlayer.getChannel() != null && sessionPlayer.getChannel().channel().isActive()) {
                    sendProtobufMessage(sessionPlayer.getChannel(), moveMessage);
                    broadcastCount++;
                } else {
                    log.warn("玩家 {} 的session不存在或连接已断开", player.getUsername());
                }
            }
            
            log.info("向房间 {} 中的 {} 个玩家广播了玩家 {} 的移动消息", roomId, broadcastCount, movedPlayer.getUsername());

        } catch (Exception e) {
            log.error("广播protobuf玩家移动消息失败", e);
        }
    }
    
    private void broadcastEmojiMessage(String roomId, GameProto.GameMessage emojiMessage, String excludePlayerId) {
        try {
            List<Player> roomPlayers = roomService.getPlayersInRoom(roomId);
            
            for (Player player : roomPlayers) {
                if (excludePlayerId != null && excludePlayerId.equals(player.getId())) {
                    continue;
                }
                
                Player sessionPlayer = webSocketEventListener.getPlayerById(player.getId());
                if (sessionPlayer != null && sessionPlayer.getChannel() != null && sessionPlayer.getChannel().channel().isActive()) {
                    sendProtobufMessage(sessionPlayer.getChannel(), emojiMessage);
                }
            }
        } catch (Exception e) {
            log.error("广播protobuf表情包消息失败", e);
        }
    }
    
    private void sendProtobufMessage(ChannelHandlerContext ctx, GameProto.GameMessage message) {
        try {
            byte[] data = message.toByteArray();
            ctx.channel().writeAndFlush(new BinaryWebSocketFrame(ctx.alloc().buffer().writeBytes(data)));
        } catch (Exception e) {
            log.error("发送protobuf消息失败", e);
        }
    }
    
    private void sendError(ChannelHandlerContext ctx, String errorMessage) {
        try {
            GameProto.ErrorMessage error = GameProto.ErrorMessage.newBuilder()
                .setMessage(errorMessage)
                .build();
            
            GameProto.GameMessage errorMsg = GameProto.GameMessage.newBuilder()
                .setBase(GameProto.BaseMessage.newBuilder()
                    .setType(GameProto.MessageType.ERROR)
                    .setTimestamp(System.currentTimeMillis())
                    .setFrom("SERVER")
                    .build())
                .setError(error)
                .build();
            
            sendProtobufMessage(ctx, errorMsg);
        } catch (Exception e) {
            log.error("发送protobuf错误消息失败", e);
        }
    }

    @Override
    public void handlerAdded(ChannelHandlerContext ctx) {
        String clientId = ctx.channel().id().asShortText();
        clients.put(clientId, ctx);
        log.info("客户端[{}]连接成功，当前在线人数: {}", clientId, clients.size());
    }

//    @Override
//    public void handlerRemoved(ChannelHandlerContext ctx) {
//        String clientId = ctx.channel().id().asShortText();
//        clients.remove(clientId);
//        log.info("客户端[{}]断开连接，当前在线人数: {}", clientId, clients.size());
//
//        if (webSocketEventListener != null) {
//            var player = webSocketEventListener.getPlayer(clientId);
//            if (player != null && player.getRoomId() != null) {
//                roomService.leaveRoom(player.getId());
//                List<Player> remainingPlayers = roomService.getPlayersInRoom(player.getRoomId());
//                broadcastRoomUpdate(player.getRoomId(), remainingPlayers);
//            }
//            webSocketEventListener.removeSessionPlayer(clientId);
//
//            if (clients.size() % 10 == 0) {
//                webSocketEventListener.cleanupInvalidSessions();
//                log.info("清理无效session完成，当前有效session数: {}", webSocketEventListener.getValidSessionCount());
//            }
//        }
//    }

    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
        String clientId = ctx.channel().id().asShortText();
        log.info("Protobuf WebSocket连接断开: {}", clientId);
        
        // 处理玩家断开连接
        handlePlayerDisconnect(clientId);
        
        super.channelInactive(ctx);
    }
    
    private void handlePlayerDisconnect(String clientId) {
        try {
            Player player = webSocketEventListener.getPlayerById(clientId);
            if (player != null && player.getRoomId() != null) {
                log.info("玩家 {} 断开连接，从房间 {} 移除", player.getUsername(), player.getRoomId());
                
                // 从房间中移除玩家
                if (roomService.leaveRoom(player.getId())) {
                    String roomId = player.getRoomId();
                    List<Player> remainingPlayers = roomService.getPlayersInRoom(roomId);
                    
                    // 广播给房间内剩余玩家，通知有玩家离开
                    broadcastLeaveGame(roomId, remainingPlayers, player.getId());
                    log.info("房间 {} 剩余玩家数量: {}", roomId, remainingPlayers.size());
                }
                
                // 从会话中移除玩家
                webSocketEventListener.removeSessionPlayer(clientId);
            }
        } catch (Exception e) {
            log.error("处理玩家断开连接失败", e);
        }
    }

    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
        log.error("Protobuf WebSocket异常", cause);
        ctx.close();
    }

    public static void broadcastToAll(byte[] message) {
        clients.forEach((clientId, context) -> {
            context.channel().writeAndFlush(new BinaryWebSocketFrame(context.alloc().buffer().writeBytes(message)));
        });
    }

    public static int getOnlineCount() {
        return clients.size();
    }
}
